// The NSpring Framework for .NET
// Copyright (c) 2003, Jeffrey Varszegi
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification, 
// are permitted provided that the following conditions are met:
// 
// - Redistributions of source code must retain the above copyright notice, this 
// list of conditions and the following disclaimer.
// 
// - Redistributions in binary form must reproduce the above copyright notice, this 
// list of conditions and the following disclaimer in the documentation and/or other 
// materials provided with the distribution.
// 
// - Neither the name of the NSpring project nor the names of its contributors may 
// be used to endorse or promote products derived from this software without 
// specific prior written permission from the copyright owner.
// 
// - No product derived from this software may be called "NSpring", nor may 
// "NSpring" appear in the name of such a product, without specific prior written 
// permission from the copyright owner.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
// OF THE POSSIBILITY OF SUCH DAMAGE. 

namespace NSpring.Logging.Filters {
    
using System;
using System.Collections;

/// <summary>
/// <para>
/// Matches on a set of specific Level objects.  (This is different from the basic level-filtering
/// ability of the Logger class, which simply rejects any events with Level properties lower than a specified Level.)  
/// </para>
/// If used with the predefined levels such as Level.Info etc., performance is fast,
/// the Accepts method taking in the neighborhood of 10 nanoseconds to execute
/// on a 2 GHz P4-M machine.  If used with custom levels, performance is slightly slower.
/// <para>
/// </para>
/// </summary>
public sealed class LevelFilter : Filter {
    private Hashtable levelsMap                         = new Hashtable();
    private ArrayList levelsList                        = new ArrayList();
    private Level[] levelsArray;
    private int levelFlags                              = 0;
    
    private readonly object monitor                     = new Object();
    
    /// <summary>
    /// Constructs a new instance
    /// </summary>
    private LevelFilter() {}

    /// <summary>
    /// Constructs a new instance that will match on the indicated level
    /// </summary>
    /// <param name="level">A level to add to the filter</param>
    public LevelFilter(Level level) {
        if (level == null) {
            throw new ArgumentNullException();
        }
        AddLevel(level);
    }
    
    /// <summary>
    /// Constructs a new instance that will match on the indicated level
    /// </summary>
    /// <param name="level">A level to add to the filter</param>
    /// <param name="inclusive">Whether the filter is inclusive (true) or not (false)</param>
    public LevelFilter(Level level, bool inclusive) {
        if (level == null) {
            throw new ArgumentNullException();
        }
        AddLevel(level);
        isInclusive = inclusive;
    }
    
    /// <summary>
    /// A convenience constructor to allow construction and population in one
    /// step, without the need for creating an array
    /// </summary>
    /// <param name="level1">A level to add to the filter</param>
    /// <param name="level2">A level to add to the filter</param>
    public LevelFilter(
        Level level1,
        Level level2
    ) : this(new Level[] {
        level1,
        level2
    }) {}
    
    
    /// <summary>
    /// A convenience constructor to allow construction and population in one
    /// step, without the need for creating an array
    /// </summary>
    /// <param name="level1">A level to add to the filter</param>
    /// <param name="level2">A level to add to the filter</param>
    /// <param name="inclusive">Whether the filter is inclusive (true) or not (false)</param>
    public LevelFilter(
        Level level1,
        Level level2,
        bool inclusive
    ) : this(new Level[] {
        level1,
        level2
    }, inclusive) {}
    
    
    /// <summary>
    /// A convenience constructor to allow construction and population in one
    /// step, without the need for creating an array
    /// </summary>
    /// <param name="level1">A level to add to the filter</param>
    /// <param name="level2">A level to add to the filter</param>
    /// <param name="level3">A level to add to the filter</param>
    public LevelFilter(
        Level level1,
        Level level2,
        Level level3
    ) : this(new Level[] {
        level1,
        level2,
        level3
    }) {}
    
    
    /// <summary>
    /// A convenience constructor to allow construction and population in one
    /// step, without the need for creating an array
    /// </summary>
    /// <param name="level1">A level to add to the filter</param>
    /// <param name="level2">A level to add to the filter</param>
    /// <param name="level3">A level to add to the filter</param>
    /// <param name="inclusive">Whether the filter is inclusive (true) or not (false)</param>
    public LevelFilter(
        Level level1,
        Level level2,
        Level level3,
        bool inclusive
    ) : this(new Level[] {
        level1,
        level2,
        level3
    }, inclusive) {}
    
    
    /// <summary>
    /// A convenience constructor to allow construction and population in one
    /// step, without the need for creating an array
    /// </summary>
    /// <param name="level1">A level to add to the filter</param>
    /// <param name="level2">A level to add to the filter</param>
    /// <param name="level3">A level to add to the filter</param>
    /// <param name="level4">A level to add to the filter</param>
    public LevelFilter(
        Level level1,
        Level level2,
        Level level3,
        Level level4
    ) : this(new Level[] {
        level1,
        level2,
        level3,
        level4
    }) {}
    
    
    /// <summary>
    /// A convenience constructor to allow construction and population in one
    /// step, without the need for creating an array
    /// </summary>
    /// <param name="level1">A level to add to the filter</param>
    /// <param name="level2">A level to add to the filter</param>
    /// <param name="level3">A level to add to the filter</param>
    /// <param name="level4">A level to add to the filter</param>
    /// <param name="inclusive">Whether the filter is inclusive (true) or not (false)</param>
    public LevelFilter(
        Level level1,
        Level level2,
        Level level3,
        Level level4,
        bool inclusive
    ) : this(new Level[] {
        level1,
        level2,
        level3,
        level4
    }, inclusive) {}
    
    
    /// <summary>
    /// A convenience constructor to allow construction and population in one
    /// step, without the need for creating an array
    /// </summary>
    /// <param name="level1">A level to add to the filter</param>
    /// <param name="level2">A level to add to the filter</param>
    /// <param name="level3">A level to add to the filter</param>
    /// <param name="level4">A level to add to the filter</param>
    /// <param name="level5">A level to add to the filter</param>
    public LevelFilter(
        Level level1,
        Level level2,
        Level level3,
        Level level4,
        Level level5
    ) : this(new Level[] {
        level1,
        level2,
        level3,
        level4,
        level5
    }) {}
    
    
    /// <summary>
    /// A convenience constructor to allow construction and population in one
    /// step, without the need for creating an array
    /// </summary>
    /// <param name="level1">A level to add to the filter</param>
    /// <param name="level2">A level to add to the filter</param>
    /// <param name="level3">A level to add to the filter</param>
    /// <param name="level4">A level to add to the filter</param>
    /// <param name="level5">A level to add to the filter</param>
    /// <param name="inclusive">Whether the filter is inclusive (true) or not (false)</param>
    public LevelFilter(
        Level level1,
        Level level2,
        Level level3,
        Level level4,
        Level level5,
        bool inclusive
    ) : this(new Level[] {
        level1,
        level2,
        level3,
        level4,
        level5
    }, inclusive) {}
    
    
    /// <summary>
    /// A convenience constructor to allow construction and population in one
    /// step, without the need for creating an array
    /// </summary>
    /// <param name="level1">A level to add to the filter</param>
    /// <param name="level2">A level to add to the filter</param>
    /// <param name="level3">A level to add to the filter</param>
    /// <param name="level4">A level to add to the filter</param>
    /// <param name="level5">A level to add to the filter</param>
    /// <param name="level6">A level to add to the filter</param>
    public LevelFilter(
        Level level1,
        Level level2,
        Level level3,
        Level level4,
        Level level5,
        Level level6
    ) : this(new Level[] {
        level1,
        level2,
        level3,
        level4,
        level5,
        level6
    }) {}
    
    
    /// <summary>
    /// A convenience constructor to allow construction and population in one
    /// step, without the need for creating an array
    /// </summary>
    /// <param name="level1">A level to add to the filter</param>
    /// <param name="level2">A level to add to the filter</param>
    /// <param name="level3">A level to add to the filter</param>
    /// <param name="level4">A level to add to the filter</param>
    /// <param name="level5">A level to add to the filter</param>
    /// <param name="level6">A level to add to the filter</param>
    /// <param name="inclusive">Whether the filter is inclusive (true) or not (false)</param>
    public LevelFilter(
        Level level1,
        Level level2,
        Level level3,
        Level level4,
        Level level5,
        Level level6,
        bool inclusive
    ) : this(new Level[] {
        level1,
        level2,
        level3,
        level4,
        level5,
        level6
    }, inclusive) {}
    
    /// <summary>
    /// Constructs a new instance that will match on the specified levels
    /// </summary>
    /// <param name="levels">The levels on which to match</param>
    public LevelFilter(Level[] levels) : this(levels, true) {}

    /// <summary>
    /// Creates a new instance that will match on the specified levels
    /// </summary>
    /// <param name="levels">The levels on which to match</param>
    /// <param name="inclusive">Whether the filter is inclusive (true) or not (false)</param>
    public LevelFilter(Level[] levels, bool inclusive) {
        if (levels == null) {
            throw new ArgumentNullException();
        }
        else if (levels.Length == 0) {
            throw new ApplicationException("Can't create a level filter with no levels");
        }
        Level level;
        for(int x = 0; x < levels.Length; x++) {
            level = levels[x];
            if (level != null) {
                AddLevel(level);
            }
        }
        if (levelsList.Count == 0) {
            throw new ApplicationException("Can't create a level filter with no levels");
        }
        isInclusive = inclusive;
    }

    public void AddLevel(Level level) {
        if (level == null) {
            throw new ArgumentNullException();
        }
        lock (monitor) {
            if (levelsMap.ContainsKey(level)) {
                return;
            }
            levelsMap[level] = level;
            levelsList.Add(level);
            if (level.IsPredefined) {
                levelFlags += level.Priority;
            }
            levelsArray = new Level[levelsList.Count];
            levelsList.CopyTo(levelsArray);
            Array.Sort(levelsArray);
        }
    }
    
    public void RemoveLevel(Level level) {
        if (level == null) {
            throw new ArgumentNullException();
        }
        lock (monitor) {
            if (!levelsMap.ContainsKey(level)) {
                return;
            }
            levelsMap.Remove(level);
            levelsList.Remove(level);
            if (level.IsPredefined) {
                levelFlags -= level.Priority;
            }
            levelsArray = new Level[levelsList.Count];
            levelsList.CopyTo(levelsArray);
            Array.Sort(levelsArray);
        }
    }
    
    /// <summary>
    /// The levels matched on by this filter
    /// </summary>
    public Level[] Levels {
        get {
            Level[] returnValue = new Level[levelsArray.Length];
            Array.Copy(levelsArray, 0, returnValue, 0, levelsArray.Length);
            return returnValue;
        }
    }
    
    protected override bool Matches(Event _event) {
        Level level = _event.Level;
        if (level.IsPredefined) {
            return ((levelFlags & level.Priority) > 0);
        }
        else {
            Level[] la = levelsArray;
            for(int x = 0; x < la.Length; x++) {
                if (la[x] == level) {
                    return true;
                }
            }
            return false;
        }
    }
        
}

}
